Selene Shepard поделилась ссылкой
22 октября 2013 года, 22:55
#11521: В погоне за тактами
Понадобилось мне как-то прошить одну микросхемку, поддержки которой не было ни в одном из имеющихся у меня программаторов. Алгоритм её программирования был известен. Недолгие изыскания показали, что один из имеющихся программаторов вполне бы мог справиться с задачей при наличии поддержки со стороны софта.

Изначально этот программатор разрабатывался как проект с открытым кодом и идеологией «максимальные возможности с как можно более простым для повторения аппаратным обеспечением». Следствием такого подхода стали использование порта LPT для подключения к PC, реализация устройства на микросхемах низкой степени интеграции (в основном регистры и инверторы). Вся логика управления была сосредоточена в софте. Как и случается с многими опенсорс-проектами, как только замаячила перспектива возможных доходов, всё стало потихоньку засекречиваться: сначала перестали выкладывать исходники софта, затем в софт добавили защиту от дизассемблирования и трассировки, а потом и аппаратное обеспечение для реализации подключения через USB переехало на микроконтроллер, прошивки для которого в открытом доступе не появлялись.

В инете удалось найти документик, в котором описывался протокол взаимодействия имеющейся у меня версии LPT-программатора и софта. Скорее всего, алгоритмы, содержавшиеся в документе, были получены путём анализа схемы программатора, поэтому очень многое оставалось неясным.

Я решил, что мне вполне по силам написать свою утилитку управления, реализующую алгоритм программирования для интересующей меня микросхемы. Исходников старых версий оригинального софта найти уже не удалось, да они бы и не помогли наверняка из-за серьёзных изменений в железяке. Так как заниматься этой затеей пришлось в свободное от основной работы время, процедуры управления программатором были готовы только через две недели. Настало время реализации самого алгоритма взаимодействия с микросхемой.

Процедуры чтения и стирания были реализованы без особых затруднений, а вот на самом главном — записи — всё чуть не рухнуло. Проблема была в том, что данные для записи надо было успевать отправлять в жёстко ограниченный промежуток времени — 200 мкс. Такой расторопности не способствовали ни Windows с её многозадачностью, ни мой кривой код на Delphi (скорее всего, в большей степени). Доставляло и то, что если снизить искусственные временные задержки между формированием импульсов, то их не успевала отрабатывать логика LPT-порта и программатора.

Так как оригинальный софт успешно писал микросхемы с похожим принципом записи, было понятно, что задача выполнима, а значит, предстоит оптимизация. Хоть мне и не хотелось использовать многопоточность в простой программе, но пришлось. Выигрыш по времени выполнения это дало небольшой, зато исчезло заклинивание GUI при выполнении длительных операций. Пришлось заняться оптимизацией процедур управления программатором. Самой затратной оказалась процедура передачи в программатор адреса. Необходимо было адрес очередной ячейки, выраженный 24-битным числом, делить на три части по восемь битов и побитно сдвигать в три линии передачи, сопровождая каждую посылку формированием строб-импульса. Сложность заключалась в том, что Delphi 7 позволял осуществлять операции битовой арифметики только над восьмибитными переменными, к тому же используемые линии вывода были подвязаны к битам регистра LPT-порта не по порядку.

Первоначальный вариант алгоритма формирования адреса тормозил из-за использования операций возведения в степень и округления (с его помощью удалось обойти часть ограничений на битовую арифметику). Первая пришедшая в голову идея: перенести часть кода в ASM-вставку и использовать для возведения двойки в нужную степень команду FPU FSCALE. Какой-то выигрыш это дало, но временной интервал в 200 мкс продолжал нарушался с завидной регулярностью. Когда до меня дошло, что во вставке мне никто не мешает использовать операции сдвига над DWORD, удалось полностью избавиться от операции возведения в степень, но выигрыш по времени всё равно был недостаточным. Более-менее приемлемый результат удалось получить после шести переписываний куска кода с нуля. К этому времени за пределами ASM-вставки остались только вызовы процедур формирования импульсов и временных задержек, полностью исчезли операции возведения в степень, сдвиг DWORD тоже оказался ненужным, везде, где можно, использовались предварительно рассчитанные константы. Количество ошибок, возникающих из-за нарушения предельного интервала, свелось к минимуму, что позволяло реализовать коррекцию неправильно записанных блоков при контрольном чтении. В принципе, на этом можно было бы и остановиться, так как программа уже позволяла решить поставленную задачу, но мне захотелось сделать всё красиво.

В процессе листания справочников я наткнулся на упоминание о том, что по возможности нужно выносить большие ASM-вставки в отдельные процедуры, так как наличие ассемблера внутри обычного кода вызывает дополнительные потери времени. Так как вставка являлась большей частью процедуры, легче было перенести вызов функции формирования импульсов внутрь неё. С помощью встроенного отладчика удалось подсмотреть, что параметры этим функциям передаются смешанным образом: часть через регистры, часть через стек. Нормально посчитать смещения в стеке я поленился и просто подглядел их в отладчике, зная, какие значения у какой переменной должны быть.

Первый же запуск обновлённой версии программы отправил WinXP в синий экран. Решив, что это была случайность, запустил программу снова. На этот раз результатом стал не только синий экран, но и сброс настроек BIOS. При пошаговой отладке выяснилось, что при первом вызове процедуры в функцию формирования импульсов передаются нормальные параметры, а при последующих вместо текущего адреса LPT-порта передаются абсолютно произвольные данные. Причина ошибки оказалась проста: желая освободить EBX для собственных нужд, я push’нул его содержимое в стек. При первом вызове процедуры формирования адреса указатель базы стека (который по умолчанию хранится в EBX) совершенно случайно оказался равен 378h, смещение на которое я и подсмотрел. При последующих вызовах база стека была уже другая, и вместо LPT-порта моя программа слала байты куда придётся.

После того как всё было отлажено и результат меня устроил (хотя полностью от процедуры контрольного чтения отказаться не удалось), я решил сравнить производительность своей и оригинальной софтины. Нашёл микросхему со схожим алгоритмом записи, но поддерживаемую оригинальной утилитой, и сравнил с помощью осциллографа временные интервалы формируемых сигналов. Результат ещё раз напомил мне о том, почему я перебежал из программистов в железячники: оригинальная софтина работала на 20–30 процентов быстрее моей. Могу лишь предположить, что вместо PerformanceCounter в оригинальной утилите используется какой-то ещё более точный таймер.